package com.xz.coursearrange.GA.impl;

import com.xz.coursearrange.entity.ClassTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @Description
 */
@Slf4j
@Component
public class HighSchoolSchedulingGA {

    /**
     * 可优化点
     * 1. 初始化阶段优化
     * 问题：当前初始化是随机打乱课程放入空时间段，虽然简单，但可能产生大量低质量解。
     * 启发式初始化策略：
     *      优先安排主课 → 早上
     *      体育课 → 下午
     *      避免同班连续相同课程
     * 考虑课程优先级或老师空闲表填入非固定课程
     * 好处：更好的初始种群 → 更快进化 → 更容易收敛
     *
     * 2. 硬约束冲突判断逻辑优化
     * 问题：当前冲突检测使用 HashMap + 多层循环，对于大规模班级/课节，可能效率偏低。
     * 可优化点：
     *      预构建一个三维时间表 Map<TimeIndex, Set<Teacher/Classroom>>，在安排时立即检测冲突
     *      或使用“冲突矩阵”快速判断是否有重复
     * 好处：减少冲突检查时间，提升评估效率（尤其对并行评估有好处）
     *
     * 3. 交叉与变异策略优化
     * 问题：交叉只是在父母课表中随机选其一；变异只是交换两个课节，缺乏“结构化优化”。
     * 可优化点：
     *      ➤ 更细粒度交叉：
     *          某班课程按星期划分
     *          父母按“周一~周五”切割交叉（而不是整张表）
     *      ➤ 更智能变异：
     *          基于冲突热点优先变异
     *          优先打乱冲突位置
     * 使用“平移变异”替代随机交换（如将体育从早上移到下午）
     * 好处：产生更优“子代”，避免无效扰动
     * @param classTaskList
     * @return
     */
    public Map<String, List<String>> getHighSchoolSchedule(List<ClassTask> classTaskList) {
//        GeneticAlgorithm ga = new GeneticAlgorithm(100, 0.01, 0.9, 1, 5);
        GeneticAlgorithm ga = new GeneticAlgorithm(2000, 0.9, 0.9, 5, 10);

        // 实例化 种群
        Population population = ga.initPopulation(classTaskList);

        // 评估
        // 初始化种群后必须先进行一次适应度评估（evaluate），才能进行交叉、变异等后续操作。
        /**
         * 1. 初始化种群（随机生成一堆解）  ✅
         * 2. 评估种群（每个个体计算适应度） ✅ ← 你现在就在这里
         * 3. 循环迭代：
         *    ├─ 选择（根据适应度） ← 需要已打分
         *    ├─ 交叉（产生新解）
         *    ├─ 变异（微调方案）
         *    ├─ 再次评估
         *    └─ 温度冷却 / 迭代+1
         */
        ga.evalPopulation(population);

        // 世代
        int generation = 1;
        // 最大可以迭代100次
        while (!ga.isTerminationConditionMet(generation, 100)) {
            System.out.println("Generation: " + generation + "  Best fitness: " + population.getFittest(0).getFitness());

            // 交叉
            /**
             * 交叉（Crossover）是遗传算法中的核心步骤之一，类似“基因混合”，意思是：
             * 从两个优秀的课表（个体）中选出一部分内容组合出一个新的课表（后代），让优秀基因得以传承。
             * 直接将父种群覆盖，改进使用另一个变量接收，经过变异然后子代跟父代全部个体排序适应度，高的活下来
             */
            population = ga.crossoverPopulation(population);

            // 变异
            population = ga.mutatePopulation(population);

            // 评估
            ga.evalPopulation(population);

            // 世代数加1
            generation++;

            // 模拟退火 降低温度  计划生育
            /**
             * 每轮进化后，温度降低
             * 降低探索强度，让算法逐步收敛而不是永远乱跳
             *
             * 想象你在排课，一开始你乱排没关系（温度高）——你愿意试各种奇葩组合。
             * 随着进化，你已经有不错的结果了（温度下降），你就不愿意轻易大改，变得更保守，进行微调优化。
             */
            ga.coolTemperature();
        }

        log.info("找到解决方案 的染色体为: ");
        population.getFittest(0).outString();
        System.out.println();
        log.info("找到解决方案 的最大适应度: " + population.getFittest(0).getFitness());

        if (population.getFittest(0).getFitness() < -90000) {
            throw new RuntimeException("最大适应度过小，请重新点击排课");
        }
        // 获取适应度最优地个体
        Individual fittest = population.getFittest(0);

        Map<String, String[]> chromosome = fittest.getChromosome();

        // 类型转化
        Map<String, List<String>> convertedMap = convertMap(chromosome);

        return convertedMap;
    }


    public static Map<String, List<String>> convertMap(Map<String, String[]> originalMap) {
        return originalMap.entrySet()
                .stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> IntStream.range(0, entry.getValue().length)
                                .mapToObj(i -> entry.getValue()[i] + String.format("%02d", i + 1))
                                .collect(Collectors.toList())
                ));
    }

    // 目标函数 f(x) = x^2
    //f(x) 决定搜索是“往哪里走”
    //f(x) = x²：越靠近 x = 0，值越小 → 搜索会被引导向中心
    //在最小化问题中，f(x) 的值越小，表示解越优。
    public static double f(double x) {
        return x * x;
    }


    public static void main(String[] args) {
        Random rand = new Random();

        double x = rand.nextDouble() * 20 - 10; // 初始化 x ∈ [-10, 10]
        double T = 100.0;         // 初始温度
        double Tmin = 1e-8;       // 最低温度
        double alpha = 0.98;      // 冷却速率
        int maxIter = 1000;       // 最大迭代次数

        double bestX = x;
        double bestF = f(x);

        for (int i = 0; i < maxIter && T > Tmin; i++) {
            double newX = x + rand.nextGaussian(); // 在邻域随机扰动
            if (newX < -10) newX = -10;
            if (newX > 10) newX = 10;

            //计算新解和旧解之间的质量差
            double delta = f(newX) - f(x); // 差值
            double exp = Math.exp(-delta / T);
            if (delta < 0 || exp > rand.nextDouble()) {
                x = newX; // 接受新解
                if (f(x) < bestF) {
                    bestX = x;
                    bestF = f(x);
                }
            }

            T *= alpha; // 降温
        }

        System.out.printf("最小值点 x = %.6f, f(x) = %.6f\n", bestX, bestF);
    }
}
